unit oPhysicalEntity;

interface
uses oIStoreable, oSerializer, oCoordinates, Windows, Messages, Classes,
SysUtils, MMSystem, XMLintf, math, oEntity;
const
  EPSILON : real = 0.00001;
  PHYSICAL_ENTITY = 0;
  zero : real = 0.0;
  // Our custom FVF, which describes our custom vertex structure
    type

    cPoint = record
     x, y : real;
    end;

    cRect = record
      TopLeft, BottomRight : TCoordinates;
    end;



    // represents physical entities in the world
    type TPhysicalEntity = class(TInterfacedObject, IStoreable)
    private
        aCenter, aTopLeft, aBottomRight : TCoordinates; // points create rect boundaries
        aID: Integer;    // obstacle id
        aPosition: TCoordinates; // centre of an obstacle
        aLogEntity : TEntity; // list of log entities on obstacle
    protected
        // tests if circle defined as paPos and paScope intersects with line [paX1, paX2]
        function circleToLine(paX1, paX2, paPos : TCoordinates; paScope : real) : boolean;
        // tests if line [pax1,pay1] intersects with line [pax2, pay2]

        procedure ClosestPointOnSegmentFromPoint(const x1,y1,x2,y2,Px,Py:real; out Nx,Ny:real);
        class function LayDistance(const x1,y1,x2,y2: real): real;
//        function fillLine(pA, pB : Tpoint;grid : cGrid; value : integer) : boolean;
    public
        Rect: cRect;
        // tests if a paTest is within a circle defined as paCentre with paRadius
        class function isInCircle(paCentre : TCoordinates; paRadius : real; paTest : TCoordinates) : boolean;
        class function IntersectionPoint(const x1, y1, x2, y2 : TCoordinates): TCoordinates;
        // test equality of two pointes with const. ESPILON as deviation
        class function pointequal(a, b : TCoordinates) : boolean;overload;
        // test equality of 2 Tpoints
        class function pointequal(a, b : TPoint) : boolean;overload;
        class function normalizeVector(paVector : TCoordinates) : TCoordinates;
        class function lineToLine(paX1, paX2, paY1, paY2: TCoordinates): boolean;
        class function pointInPolygon(paPoint : TCoordinates; paPoints : TList): boolean;
        // test if paPoint is within paRect
        class function isInner(paRect : cRect; paPoint : TCoordinates) : Boolean; overload;
        class function getDistance(paA, paB : TCoordinates) : Real;
        class function Intersect(const x1,y1,x2,y2,x3,y3,x4,y4: real) : boolean;
        // stores paPoint as element with name - tag in a paNode
        class procedure addPoint(paTag : string; paPoint : TCoordinates; var paNode : IXMLnode);overload;
        class procedure getPoint(var paPoint : TCoordinates; paNode : IXMLnode);overload;
        class procedure getPoint(paTag : string; var paPoint : TCoordinates; paNode : IXMLnode);overload;
        property ID: integer read aID write aID;
        property Position: TCoordinates read aPosition write aPosition;
        property TopLeft : TCoordinates read aTopLeft write aTopLeft;
        property BottomRight : TCoordinates read aBottomRight write aBottomRight;
        property LogicalEntity : TEntity read aLogEntity write aLogEntity;
        property Center : TCoordinates read aCenter write aCenter;
        procedure draw; virtual;
        constructor create(start: TCoordinates);overload;
        constructor create; overload;
        function Intersects(paObstacle : TPhysicalEntity) : boolean; virtual; abstract;
        procedure Resize(paRatio : real); virtual;abstract;
//        function CanBeInserted(grid : cGrid) : boolean ; virtual;abstract;
        procedure SaveToXML(var paNode : Ixmlnode); virtual;
        procedure LoadFromXML(paNode : IXMLNode); virtual;
        function isInner(paObstacle : TPhysicalEntity): boolean; overload; virtual; abstract;
        // test if this and paobstacle equals
        function equalsEntity(paObstacle : TPhysicalEntity) : boolean; virtual; abstract;
        // test if an obstacle is part of a paSector
        function IsPartOfSector(paSector : cRect) : boolean; virtual; abstract;
        // test if this obstacle is in a circle defined as paPos with paScope as radius
        function isWithinCircle(paPos : TCoordinates; paScope : real) : boolean; virtual; abstract;
        function ToString : String; override;
        destructor Done;
    end;
implementation

{ cObstacle }


class function TPhysicalEntity.getDistance(paA, paB: TCoordinates): Real;
begin
  Result := Sqrt(sqr(paA.Y - paB.Y) + sqr(paA.X - paB.X));
end;

class procedure TPhysicalEntity.getPoint(paTag: string;
  var paPoint: TCoordinates; paNode: IXMLnode);
begin
  TSerializer.loadValueFromXML(paNode.ChildNodes[paTag], 'x', paPoint.X);
  TSerializer.loadValueFromXML(paNode.ChildNodes[paTag], 'y', paPoint.Y);
end;

class procedure TPhysicalEntity.getPoint(var paPoint: TCoordinates; paNode: IXMLnode);
begin
  TSerializer.loadValueFromXML(paNode, 'x', paPoint.X);
  TSerializer.loadValueFromXML(paNode, 'y', paPoint.Y);
end;

class function TPhysicalEntity.LayDistance(const x1,y1,x2,y2: real): real;
var
  dx : real;
  dy : real;
begin
  dx := (x2 - x1);
  dy := (y2 - y1);
  Result := dx * dx + dy * dy;
end;

class function TPhysicalEntity.Intersect(const x1, y1, x2, y2, x3, y3, x4,
  y4: real): boolean;
var
  UpperX : real;
  UpperY : real;
  LowerX : real;
  LowerY : real;
  Ax     : real;
  Bx     : real;
  Cx     : real;
  Ay     : real;
  By     : real;
  Cy     : real;
  D      : real;
  F      : real;
  E      : real;
begin
  Result := false;

  Ax := x2 - x1;
  Bx := x3 - x4;

  if Ax < Zero then
  begin
    LowerX := x2;
    UpperX := x1;
  end
  else
  begin
    UpperX := x2;
    LowerX := x1;
  end;

  if Bx > Zero then
  begin
    if (UpperX < x4) or (x3 < LowerX) then
     Exit;
  end
  else if (Upperx < x3) or (x4 < LowerX) then
    Exit;

  Ay := y2 - y1;
  By := y3 - y4;

  if Ay < Zero then
  begin
    LowerY := y2;
    UpperY := y1;
  end
  else
  begin
    UpperY := y2;
    LowerY := y1;
  end;

  if By > Zero then
  begin
    if (UpperY < y4) or (y3 < LowerY) then
      Exit;
  end
  else if (UpperY < y3) or (y4 < LowerY) then
    Exit;

  Cx := x1 - x3;
  Cy := y1 - y3;
  d  := (By * Cx) - (Bx * Cy);
  f  := (Ay * Bx) - (Ax * By);

  if f > Zero then
  begin
    if (d < Zero) or (d > f) then
     Exit;
  end
  else if (d > Zero) or  (d < f) then
    Exit;

  e := (Ax * Cy) - (Ay * Cx);

  if f > Zero then
  begin
    if (e < Zero) or (e > f) then
      Exit;
  end
  else if(e > Zero) or (e < f) then
    Exit;

  Result := true;
end;

class function TPhysicalEntity.IntersectionPoint(const x1, y1, x2,
  y2: TCoordinates): TCoordinates;
var
  dx1,
  dx2,
  dx3,
  dy1,
  dy2,
  dy3,
  det,
  ratio : real;
  p : TCoordinates;
begin
  p := nil;
  Result := p;
  dx1 := x1.x - y1.x;
  dx2 := x2.x - y2.x;
  dx3 := y2.x - y1.x;
  dy1 := x1.y - y1.y;
  dy2 := x2.y - y2.y;
  dy3 := y2.y - y1.y;

  det := (dx2 * dy1) - (dy2 * dx1);

  if abs(det - 0) <= EPSILON then
  begin
    if Abs((dx2 * dy3) - (dy2 * dx3)) <= EPSILON then
    begin
      p := TCoordinates.Create;
      p.x := x2.x;
      p.y := x2.y;
      Result := p;
      Exit;
    end
  else
    Exit;
  end;

  ratio := ((dx1 * dy3) - (dy1 * dx3)) / det;
  p := TCoordinates.Create;
  p.x := (ratio * dx2) + y2.x;
  p.y := (ratio * dy2) + y2.y;
  Result := p;
end;

constructor TPhysicalEntity.create(start: TCoordinates);
begin
    create;
end;


procedure TPhysicalEntity.draw;
begin

end;


procedure TPhysicalEntity.LoadFromXML(paNode: IXMLNode);
begin
// class procedure addPoint(tag : string; point : cPoint; var node : IXMLnode);overload;
   //     class procedure getPoint(tag : string; var point : cPoint; node : IXMLnode);overload;

  getPoint('position',aPosition, paNode);
  getPoint('topleft',aTopLeft, paNode);
  getPoint('bottomright',aBottomRight, paNode);
  getPoint('center', aCenter, paNode);
  aID := paNode.Attributes['id'];
  aLogEntity := nil;
  //aLogEntity := TLogicalEntity.Create;
  //aLogEntity.LoadFromXML(paNode.ChildNodes['logical_entity']);
end;

class function TPhysicalEntity.normalizeVector(
  paVector: TCoordinates): TCoordinates;
var
  length : real;
begin
  with paVector do begin
    length := Sqrt( x * x + y * y);
    Result := TCoordinates.Create(x / length, y / length);
  end;
end;

procedure TPhysicalEntity.SaveToXML(var paNode : IXmlnode);
begin
  addPoint('position', aPosition, paNode);
  addPoint('topleft', aTopLeft, paNode);
  addPoint('bottomright', aBottomRight, paNode);
  addPoint('center', aCenter, paNode);
  paNode.Attributes['id'] := aId;
  //n1 := paNode.AddChild('logical_entity');
  //aLogEntity.SaveToXML(n1);
end;

constructor TPhysicalEntity.create;
begin
  aPosition := TCoordinates.Create(0, 0);
  aID := PHYSICAL_ENTITY;
  aCenter := TCoordinates.Create;
  aTopLeft := TCoordinates.Create(0, 0);
  aBottomRight := TCoordinates.Create(0, 0);
  Rect.TopLeft := TopLeft;
  Rect.BottomRight := BottomRight;
end;

destructor TPhysicalEntity.Done;
begin
  aCenter.Free;
  aPosition.Free;
  aTopLeft.Free;
  aBottomRight.Free;
end;

function TPhysicalEntity.ToString: String;
var
  s : string;
begin
  s := '[' + Floattostr(aPosition.x) + ',' + Floattostr(aPosition.y) +']';
  ToString := s;
end;

procedure TPhysicalEntity.ClosestPointOnSegmentFromPoint(const x1,y1,x2,y2,Px,Py:real; out Nx,Ny:real);
var
  Vx    : real;
  Vy    : real;
  Wx    : real;
  Wy    : real;
  c1    : real;
  c2    : real;
  Ratio : real;
begin
  Vx := x2 - x1;
  Vy := y2 - y1;
  Wx := Px - x1;
  Wy := Py - y1;

  c1 := Vx * Wx + Vy * Wy;

  if c1 <= 0.0 then
  begin
    Nx := x1;
    Ny := y1;
    Exit;
  end;

  c2 := Vx * Vx + Vy * Vy;

  if c2 <= c1  then
  begin
    Nx := x2;
    Ny := y2;
    Exit;
  end;

  Ratio := c1 / c2;

  Nx := x1 + Ratio * Vx;
  Ny := y1 + Ratio * Vy;
end;

function TPhysicalEntity.circleToLine(paX1, paX2, paPos: TCoordinates;
  paScope: real): boolean;
var
  clo, diff, dir : cPoint;
  t, d : real;
  x1, x2, y1, y2, dx, dy : real;
  Px : real;
  Py : real;
begin
  ClosestPointOnSegmentFromPoint(paX1.x,paX1.y,paX2.x,paX2.y, paPos.x, paPos.y, Px,Py);
  Result := (LayDistance(Px,Py,paPos.x,paPos.y) <= (paScope * paScope));
end;

{
function cObstacle.fillLine(pA, pB: Tpoint; grid: cGrid;
  value: integer): boolean;
var
  a, b : Tpoint;
  k,d : real;
  j,i,l : integer;
  match : boolean;
begin
    match := true;
    
        a := pA;
        b := pB;
        if (a.X <> b.X) then
         begin
          if a.X > b.X then     // zoradim poda x
           begin
            a := pB;
            b := pA;
           end;
          d := (B.Y - A.Y) / (B.X - A.X);
          i := a.Y;
          k := i;
          for j:= a.X to b.X do
           begin

            if (j >= 0) and (i >= 0) and (j < length(grid)) and (i < length(grid[0]))
            then
             if (value <> -1) then
              begin
               grid[j, i].value := value;
               if (value = 1) then grid[i, j].barriers.Add(self)
               else grid[i, j].barriers.Remove(self);
              end
             else
              if grid[j, i].value <> 0 then match := false;

            if (d > 0) then
             begin
              for l:= i to round(i + d/2) do
               if (j >= 0) and (i >= 0) and (j < length(grid)) and (i < length(grid[0]))
               then
                 if (value <> -1) then
                  begin
                   grid[j, i].value := value;
                   if (value = 1) then grid[i, j].barriers.Add(self)
                   else grid[i, j].barriers.Remove(self);
                  end
                 else
                  if grid[j, i].value <> 0 then match := false;
              k := k + d;
              i := round(k);
             end
            else
             begin
              for l:= i downto floor(k) do
               if (j >= 0) and (i >= 0) and (j < length(grid)) and (i < length(grid[0]))
               then
                if (value <> -1) then
                begin
                  grid[j, i].value := value;
                  if (value = 1) then grid[i, j].barriers.Add(self)
                  else grid[i, j].barriers.Remove(self);
                 end
               else
                if grid[j, i].value <> 0 then match := false;

              k := k + d;
              i := round(k);
             end;
           end
          end
         else             // a.x = b.x
          begin
            i := a.X;
            if a.Y > b.Y then     // zoradim poda x
             begin
              a := pB;
              b := pA;
             end;
            for j:= a.Y to b.Y do
            if (j >= 0) and (i >= 0) and (j < length(grid)) and (i < length(grid[0]))
            then
             if (value <> -1) then
              begin
               grid[j, i].value := value;
               if (value = 1) then grid[i, j].barriers.Add(self)
               else grid[i, j].barriers.Remove(self);
              end
             else
              if grid[j, i].value <> 0 then match := false;
          end;

   fillLine := match;
end;
}

class function TPhysicalEntity.isInner(paRect: cRect; paPoint: TCoordinates): Boolean;
begin
if (paPoint.X >= paRect.TopLeft.x) and (paPoint.X <= paRect.BottomRight.x) and (paPoint.Y <= paRect.TopLeft.y) and (paPoint.Y >= paRect.BottomRight.y) then
    isInner := true
  else
    isInner := false;
end;

class function TPhysicalEntity.lineToLine(paX1, paX2, paY1, paY2: TCoordinates): boolean;
begin
  Result := Intersect(pax1.X, pax1.Y, pax2.X, pax2.Y, pay1.X, pay1.Y, pay2.X, pay2.Y);
end;

class procedure TPhysicalEntity.addPoint(paTag: string; paPoint: TCoordinates; var paNode: IXMLnode);
   var n : IXMLNode;
begin
  n := paNode.AddChild(paTag);
  TSerializer.saveValueToXML(n, 'x', paPoint.X);
  TSerializer.saveValueToXML(n, 'y', paPoint.Y);
end;

class function TPhysicalEntity.pointequal(a, b: TCoordinates): boolean;
begin
  if (abs(a.x - b.x) < EPSILON) and (abs(a.y - b.y) < EPSILON) then
    result := true
  else
    result := false;
end;

class function TPhysicalEntity.pointequal(a, b: TPoint): boolean;
begin
  if (a.x = b.x) and (a.y = b.y) then
    result := true
  else
    result := false;

end;

class function TPhysicalEntity.pointInPolygon(paPoint: TCoordinates;
  paPoints: TList): boolean;
var
  i, j : integer;
  p, p2 : TCoordinates;
begin
 Result := False;
  if paPoints.Count < 3 then Exit;
  j := paPoints.Count - 1;

  for i := 0 to paPoints.Count - 1 do
  begin
    p := paPoints[i];
    p2 := paPoints[j];
    if ((p.y <= paPoint.y) and (paPoint.Y < p2.y)) or    // an upward crossing
       ((p2.y <= paPoint.Y) and (paPoint.Y < p.y)) then  // a downward crossing
    begin
      (* compute the edge-ray intersect @ the x-coordinate *)
      if (paPoint.x - p.x < ((p2.x - p.x) * (paPoint.Y - p.y) / (p2.y - p.y))) then
        Result := not Result;
    end;
    j := i;
  end;

end;

class function TPhysicalEntity.isInCircle(paCentre: TCoordinates; paRadius: real;
  paTest: TCoordinates): boolean;
begin
  if sqrt(power(paCentre.x - paTest.x, 2) + power(paCentre.y - paTest.y, 2)) <= paRadius  then
    Result := true
  else
    Result := false;
end;

end.
